/*
 * Copyright (c) 2010, artur
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * kiddie-os
 * Created on: 16.04.2010
 *     Author: artur
 *
 */

#include <x86/ArchCpu.h>
#include <irq/EventController.h>
#include <sys/sched/Scheduler.h>
#include <hw/Platform.h>
#include <sys/sched/task/Task.h>

extern kernel::Platform hwplatform;
extern kernel::sys::sched::task::Task *current;
extern kernel::sys::sched::task::Task *trash;

namespace kernel
{
  namespace cpu
  {

    ArchCpu::ArchCpu()
    {
    }

    ArchCpu::~ArchCpu()
    {
    }

    int
    ArchCpu::_task_init(kernel::sys::sched::task::Task *tsk)
    {

      uint32_t flags, *adr;
      struct context *_newctx;
      struct kern_regs *_newkregs;

      flags = get_eflags();

      interrupt_disable();

      ::hwplatform.console << kernel::Console::HEX
          << "[ ArchCpu ] _task_init(): entry(0x" << (int) tsk->entry
          << "), PL(0x" << (int) tsk->pl << "), CTX.U_STACK(0x"
          << (int) tsk->tsk_context.u_stack_top << ")\n";

      _newctx = (struct context*) (&tsk->tsk_context);
      _newkregs = (struct kern_regs*) (&_newctx->kregs);
      _newctx->uregs = (struct cpu_regs*) ((_newctx->k_stack_top
          - sizeof(struct cpu_regs)));
      switch (tsk->pl)
        {
      /*
       * TODO IMPORTANT!!! Think hardly on where kernel task stack must be placed?
       */
      case kernel::sys::sched::task::Task::KERNEL:
        _newctx->uregs->cs = (uint32_t) KERNEL_CS;
        _newctx->uregs->ds = _newctx->uregs->es = (uint32_t) KERNEL_DS;
        _newctx->uregs->eflags = (uint32_t)(EFL_IF | EFL_IOPL_KERN);
        /*
         * FIXME Critical!!!
         * If task is running in kernel mode,
         * _newctx->uregs->esp points to return address
         * _newctx->uregs->ss points to first argument.
         * Warning!! Seems to be potential secuirty hole
         */
#warning "Warning!! Seems to be a potential secuirty hole"
        _newctx->uregs->esp = (uint32_t) ArchCpu::_task_end;
        _newctx->uregs->ss = (uint32_t) NULL;
        break;

      case kernel::sys::sched::task::Task::USER:
      default:
        _newctx->uregs->cs = (uint32_t)(USER_CS | 3);
        _newctx->uregs->ds = _newctx->uregs->es = (uint32_t)(USER_DS | 3);
        _newctx->uregs->ss = (uint32_t)(USER_DS | 3);
        _newctx->uregs->esp = (uint32_t) _newctx->u_stack_top;
        _newctx->uregs->eflags = (uint32_t)(EFL_IF | EFL_IOPL_USER);
        break;
        }

      _newctx->uregs->eip = (uint32_t)(tsk->entry);
      _newctx->uregs->eax = 0;
      _newctx->esp0 = (uint32_t)(_newctx->k_stack_top);

      // initial return for first stack switching of this task
      _newkregs->eip = (uint32_t) & syscall_ret;
      _newkregs->esp = (uint32_t)(_newctx->uregs) - sizeof(uint32_t);

      interrupt_enable();
    }

    void
    ArchCpu::_swap(kernel::sys::sched::task::Task *prev,
        kernel::sys::sched::task::Task *next)
    {
      set_cr3((uint32_t)(next->vmap.pgd));
      cpu_switch(&prev->tsk_context.kregs, &next->tsk_context.kregs);
    }

    void
    ArchCpu::_task_end(void)
    {
      ::hwplatform.console << "[" << __FILE__ << "]" << __FUNCTION__ << "()\n";

      ::hwplatform.eventController << kernel::irq::EventController::OFF;
      *::hwplatform.scheduler -= ::current;
      ::hwplatform.eventController << kernel::irq::EventController::OFF;
      ::current = ::trash;
      *::hwplatform.scheduler << kernel::cmd::CONTINUE;
    }

  }
}
